﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization;
using System.Threading;
using System.Text.RegularExpressions;
using System.Messaging;

namespace SiemensTest
{
    public partial class SetRecord : Form
    {
        // global inventory and index
        private Inventory g_inventory;
        private int g_currentindex;

        private bool g_queueOwner;
        private MessageQueue g_messagequeue;

        private bool g_bNewRecord = false;
        private bool g_bUpdateRecord = true;

        private Thread g_thread;

        // The user close the forms
        private void SetRecord_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (g_thread!=null) g_thread.Abort();
        }

        private delegate void MethodInvoker(Employee employee);
        private void UpdateListArray(Employee employee)
        {
            // Let us go over the messages, it will indicate which employ is now
            // reviewable.
            for (int i=0;i<g_inventory.InventoryItems.Length;i++)
            {
                if (employee.name.Equals(g_inventory.InventoryItems[i].name))
                {
                    if (employee.position.Equals(g_inventory.InventoryItems[i].position) &&
                        employee.employeeid.Equals(g_inventory.InventoryItems[i].employeeid) &&
                        employee.date.Equals(g_inventory.InventoryItems[i].date))
                    {
                        // Let us make sure there is no content chagne for teh message itself
                        // Then this object is intended for reviewable change.
                        if (employee.viewable != g_inventory.InventoryItems[i].viewable)
                        {
                            g_inventory.InventoryItems[i].viewable = employee.viewable;
                        }
                    }

                    if (!employee.position.Equals(g_inventory.InventoryItems[i].position) ||
                        !employee.employeeid.Equals(g_inventory.InventoryItems[i].employeeid) ||
                        !employee.date.Equals(g_inventory.InventoryItems[i].date))
                    {
                        // There is update to the data
                        g_inventory.InventoryItems[i].position = employee.position;
                        g_inventory.InventoryItems[i].employeeid = employee.employeeid;
                        g_inventory.InventoryItems[i].date = employee.date;
                    }

                }
            }
        }


        //
        // Function: 
        // (1) This function is used to trace the message queue
        //           It calls the delegate function in order to update the ListView
        // (2) This function is used to trace the check status for the listView
        //           a certain item is checked or unchecked, then the check status
        //           should be reported to a queue and then the status is updated accordingly
        private void PeekMessages()
        {
            // Connect to the message queue
            MessageQueue messagequeue = new MessageQueue(@".\Private$\RecordsTestQueue");
            while (messagequeue != null)
            {
                System.Type[] types = new Type[1];
                types[0] = typeof(Employee);
                messagequeue.Formatter = new XmlMessageFormatter(types);
                foreach (System.Messaging.Message message in messagequeue)
                {
                    // The key here is the latest employee object is saved
                    // into the list view itself. Let us update the 
                    // list view directly and also update the employee object
                    Employee employee = message.Body as Employee;
                    Invoke(new MethodInvoker(UpdateListArray), employee);
                }
                // Sleep for a certain period
                Thread.Sleep(5000);
            }
        }


        //
        // This function is defined to get the Employee records from employees.xml file
        // If there is no record, then the next and previous button should be also disabled
        // until a record is avaiable for viewing.
        //
        public void getEmployeeRecords()
        {
            FileStream f= null;
            try
            {
                // This function is used to get the employess from the XML file
                // Create the atributes
                XmlAttributes attrs = new XmlAttributes();
                attrs.XmlElements.Add(new XmlElementAttribute("Employee", typeof(Employee)));

                // Ceate the overrids 
                XmlAttributeOverrides attOver = new XmlAttributeOverrides();
                attOver.Add(typeof(Inventory), "InventoryItems", attrs);

                // Output to employees.xml 
                f = new FileStream("c:\\employees.xml", FileMode.OpenOrCreate);
                XmlSerializer sr = new XmlSerializer(typeof(Inventory), attOver);
                g_inventory = (Inventory)sr.Deserialize(f);
                f.Close();

                // OK, now we have get the data and we should initial the last record on the screen.
                int length = g_inventory.InventoryItems.GetLength(0);
                if (length > 0)
                {
                    Employee employee = g_inventory.InventoryItems[length - 1];
                    if (employee.viewable)
                    {
                        textBoxEmployee.Text = employee.name;
                        textNumber.Text = employee.employeeid;
                        textPosition.Text = employee.position;
                        textStartDate.Text = employee.date;

                        // Disable the employee field.
                        textBoxEmployee.Enabled = false;
                    }
                    else
                    {
                        textBoxEmployee.Text = "";
                        textNumber.Text = "";
                        textPosition.Text = "";
                        textStartDate.Text = "";
                        textBoxEmployee.Enabled = false;
                        textNumber.Enabled = false;
                        textPosition.Enabled = false;
                        textStartDate.Enabled = false;
                    }

                    // move it to the last position
                    g_currentindex = length - 1;

                    labelPosition.Text = String.Format("{0}/{1}", g_currentindex, g_currentindex);
                    labelPosition.Visible = true;
                    // buttonNext.Text = ">|";
                }
                else
                {
                    buttonNext.Enabled = false;
                    buttonPrevious.Enabled = false;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                // close the file handle
                if(f!=null) f.Close();
            }
        }

        //
        // The constructor the class to initial some default values.
        //
        public SetRecord()
        {    
            InitializeComponent();

            // by default, the save button is disabled.
            // buttonSave.Enabled = false;

            // We need to get the list of emmployess out and create an array
            g_inventory = new Inventory();
            g_currentindex = -1;
            // g_inventory.InventoryItems = new LinkedList<Employee>();
            // g_inventory.InventoryItems = new Employee[1];
        }

        private void buttonNew_Click(object sender, EventArgs e)
        {
            // Put a warning symbol in the window
            // the user decides to add a new record
            g_bNewRecord = true;

            // Clean up the value and allow the user to input the new values
            textBoxEmployee.Text = "";
            textNumber.Text = "";
            textPosition.Text = "";
            textStartDate.Text = "";

            // Enable all the edit boxes so that the user can input the new values
            bool enable = true;
            textBoxEmployee.Enabled = enable;
            textNumber.Enabled = enable;
            textPosition.Enabled = enable;
            textStartDate.Enabled = enable;

            buttonSave.Enabled = true;
            richTextBoxMsg.Visible = true;
        }

        //
        // The Event handler for the "Save" button
        // 1. It handles the data validation
        // 2. It opens a mutex to ensure this is only proces for updating the XML file
        // 3. Enable the navigation buttons.
        // 4. Disable to Save button.

        private void buttonSave_Click(object sender, EventArgs e)
        {
            // display the warning message
            richTextBoxMsg.Visible = true;

            // validate the user input
            string name = textBoxEmployee.Text;
            string employeeid = textNumber.Text;
            string position = textPosition.Text;
            string date = textStartDate.Text;

            // Trucate the strings
            String[] splitstring = name.Split(' ');
            name = splitstring[0];
            if (!Regex.IsMatch(name, "^[A-Za-z]*$"))
            {
                MessageBox.Show("Employee name is not valid");
                return;
            }

            // Get the first 4 character
            if (employeeid.Length!= 4 || !Regex.IsMatch(employeeid, "^[0-9]*$"))
            {
                MessageBox.Show("Employee ID is not valid");
                return;
            }

            splitstring = position.Split(' ');
            position = splitstring[0];
            if (!Regex.IsMatch(position, "^[A-Za-z0-9]*$"))
            {
                MessageBox.Show("Employee position is not valid");
                return;
            }

            // It is very important to fully ensure this is the only process, which is trying to access
            // to update the employees.xml files. The approach we use here is Mutext and this is the easiest 
            // way to block the other process from updating the same file.
            Mutex mutex = null;
            const string MutextName = "SIMENS_TEST_SERVER";

            try
            {
                mutex = Mutex.OpenExisting(MutextName);
            }
            catch(WaitHandleCannotBeOpenedException)
            {
                // cannot open the mutext because it does not exist, then this is the right time to update the file.
            }

            if (mutex == null)
            {
                mutex = new Mutex(true, MutextName);
                Console.WriteLine("Start to update the XML file");
            }
            else
            {
                // Close the mutext and return, this means the file is being updated by another file.
                mutex.Close();
                Console.WriteLine("The file is being updated by another process");
                return;
            }

            // once the content is saved to c:\employees.xml file
            // then the Save button is disabled again.

            // Create the object and assign the value from the text control
            Employee employee = new Employee();
            employee.name = name;
            employee.employeeid = employeeid;
            employee.position = position;
            employee.date = date;

            // The user decides to add a new record
            if (g_bNewRecord)
            {
                // when it is created through the save button for the new entry the content is not viewable.
                employee.viewable = false;

                // It is ready to serialize the employees.
                // cover it to a list first, add the employee and then put it back to array.
                if (g_inventory.InventoryItems == null)
                {
                    // locate the first element
                    Employee[] employees = { employee };
                    g_inventory.InventoryItems = employees;
                }
                else
                {
                    // locate other elements
                    List<Employee> list = (List<Employee>)g_inventory.InventoryItems.ToList();
                    list.Add(employee);
                    g_inventory.InventoryItems = list.ToArray();
                }

                // Add the entry to the array
                // Move the current position to the last position
                // [0.....index-1]
                g_currentindex = g_inventory.InventoryItems.GetLength(0)-1;
            } 
            else
            {
                // If it is not a new record, then it is an existing record.
                // copy the content over
                // We should double check the content. If it is the same, then there is 
                // no point of updating the record.
                Employee em = g_inventory.InventoryItems[g_currentindex];
                if (em.employeeid != employee.employeeid ||
                    em.position != employee.position ||
                    em.date != employee.date)
                {
                    g_inventory.InventoryItems[g_currentindex] = employee;
                }
                else
                {
                    MessageBox.Show("No change to the employ information");

                    // close the mutex
                    mutex.Close();
                    return;
                }
            }

            // Create the atributes
            XmlAttributes attrs = new XmlAttributes();
            attrs.XmlElements.Add(new XmlElementAttribute("Employee", typeof(Employee)));

            // Ceate the overrids 
            XmlAttributeOverrides attOver = new XmlAttributeOverrides();
            attOver.Add(typeof(Inventory), "InventoryItems", attrs);

            // Output to employees.xml
            TextWriter tr = new StreamWriter("c:\\employees.xml");
            XmlSerializer sr = new XmlSerializer(typeof(Inventory), attOver);
            sr.Serialize(tr, g_inventory);
            tr.Close();

            g_messagequeue.Send(employee, "Employee Information");

            // Enable and disable buttons
            // the record is saved.
            buttonPrevious.Enabled = true;
            buttonNext.Enabled = true;

            // Let us close the mutex here so that it can be updated by other applications.
            mutex.Close();

            // A new record is added, now reset it to the original value
            if(g_bNewRecord) g_bNewRecord = false;
        }

        private void updateEmployeControls(bool enable)
        {
            // The employee text box is only available when the user presses new record
            // textBoxEmployee.Enabled = enable;
            textNumber.Enabled = enable;
            textPosition.Enabled = enable;
            textStartDate.Enabled = enable;
            if (!enable)
            {
                // Disable the content, it is not visiable to the user.
                textBoxEmployee.Text = "";
                textNumber.Text = "";
                textPosition.Text = "";
                textStartDate.Text = "";
            }
        }
        // 
        // Event handler for the Previous button
        //
        private void buttonPrevious_Click(object sender, EventArgs e)
        {
            g_bNewRecord = false;
            g_bUpdateRecord = true;
            // For updating the record, the use is not allowed to change the employee name. 
            textBoxEmployee.Enabled = false;

            richTextBoxMsg.Visible = false;

            int length = g_inventory.InventoryItems.GetLength(0)-1;
            if (g_currentindex > 0)
            {
                g_currentindex--;
                Employee employee = g_inventory.InventoryItems[g_currentindex];
                textBoxEmployee.Text = employee.name;
                textNumber.Text = employee.employeeid;
                textPosition.Text = employee.position;
                textStartDate.Text = employee.date;
                updateEmployeControls(employee.viewable);
                buttonSave.Enabled = true;
                labelPosition.Text = String.Format("{0}/{1}", g_currentindex, length);
                labelPosition.Visible = true;
            }
        }

        //
        // Event handler for the Next button
        // It should judge whether 
        // 
        private void buttonNext_Click(object sender, EventArgs e)
        {
            g_bUpdateRecord = true;
            g_bNewRecord = false;
            // The user is not allowed to change the employee name in this case.
            textBoxEmployee.Enabled = false;

            richTextBoxMsg.Visible = false;
            int maxlocation = g_inventory.InventoryItems.GetLength(0) - 1;
            if (g_currentindex < maxlocation)
            {
                // move to the next record
                g_currentindex++;
                Employee employee = g_inventory.InventoryItems[g_currentindex];
                textBoxEmployee.Text = employee.name;
                textNumber.Text = employee.employeeid;
                textPosition.Text = employee.position;
                textStartDate.Text = employee.date;
                updateEmployeControls(employee.viewable);
                buttonNext.Text = ">";
                //if (g_currentindex == maxlocation)
                //    buttonNext.Text = ">|";
                buttonSave.Enabled = true;
                labelPosition.Text = String.Format("{0}/{1}", g_currentindex, maxlocation);
                labelPosition.Visible = true;
            }
        }

        //
        // When the form is loaded, it is ready to read the data
        //
        private void SetRecord_Load(object sender, EventArgs e)
        {
            // Create a message queue if there is no message queue.
            // The purpose of this queue is to queue the object update status
            // so that all the applications can pick up the information 
            // from this queue and update the information properly
            if (!MessageQueue.Exists(@".\Private$\RecordsTestQueue"))
            {
                MessageQueue.Create(@".\Private$\RecordsTestQueue");
                g_queueOwner = true;
                // This function is used to delete the message queue
                // MessageQueue.Delete(@".\Private$\RecordsTestQueue");
            }
            g_messagequeue = new MessageQueue(@".\Private$\RecordsTestQueue");

            // Get a number of records from the existing XML file.
            getEmployeeRecords();

            // Initial a thread to keep trace of messages
            g_thread = new Thread(PeekMessages);
            g_thread.IsBackground = true;
            g_thread.Start();
        }
    }

    // Each employee is defined as a class
    [Serializable]
    public class Employee
    {
        public string name;
        public string employeeid;
        public string position;
        public string date;
        public bool viewable; // The default value is viewable
    }

    // All the employees are considered part of the company inventory.
    [Serializable]
    public class Inventory
    {
        private Employee[] employees;
        // private List<Employee> employees;
        public Inventory() { }
        [XmlArrayItem("Employee", typeof(Employee))]
        public Employee[] InventoryItems
        {
            get { return employees; }
            set { employees = value; }
        }
    }
}
